﻿using System;
using System.IO;
using Gmantis.Zip.ZLib;

namespace Gmantis.Zip
{
    public class ZipEntry
    {
        // Fields
        internal int _attExternal;
        internal short _attInternal;
        internal string _comment;
        internal int _crc32;
        internal string _fileName;
        internal short _flags;
        internal short _method;
        internal short _modDate;
        internal short _modTime;
        internal long _offset;
        internal ZipFile _owner;
        internal long _szComp;
        internal long _szOriginal;
        internal static byte[] CENTRAL_SIGN = new byte[] { 80, 0x4b, 1, 2 };
        internal const ushort HDR_ZIP64 = 1;
        internal const int HEADERSIZE = 0x2e;
        internal static byte[] LOCAL_SIGN = new byte[] { 80, 0x4b, 3, 4 };
        internal const int LOCALHEADERSIZE = 30;
        internal const ushort ZIPVERSION = 20;
        internal const ushort ZIPVERSION64 = 0x2d;

        // Methods
        internal ZipEntry(ZipFile owner)
        {
            this._owner = owner;
        }

        private static string AdjustDirSepChar(string str)
        {
            str = str.Replace(Path.DirectorySeparatorChar, '/');
            return str.Replace(Path.AltDirectorySeparatorChar, '/');
        }

        public bool CheckCRC32()
        {
            long num = 0L;
            Stream stream = null;
            try
            {
                stream = this.OpenReader();
                CRC32 crc = new CRC32();
                long num2 = this._szOriginal;
                byte[] buffer = new byte[0x40000];
                while (num2 > 0L)
                {
                    int count = (int)Math.Min((long)buffer.Length, num2);
                    int len = stream.Read(buffer, 0, count);
                    if (len == 0)
                    {
                        goto Label_0079;
                    }
                    num2 -= (long)((ulong)len);
                    num = crc.checkSum(num, buffer, 0, len);
                }
            }
            catch
            {
                num = this._crc32 + 1;
                throw;
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                }
            }
        Label_0079:
            return (((int)num) == this._crc32);
        }

        private void CheckDouble(int size)
        {
            if (size < 8)
            {
                throw new ZipFileException(StringTables.GetString("Missing data for ZIP64 extra field"), this.FileName);
            }
        }

        public bool CheckPassword()
        {
            return this._owner.CheckPassword(this);
        }

        public void Extract(string destFileName)
        {
            this._owner.Extract(this, destFileName);
        }

        internal byte[] GetExtraFieldInfo(bool central, out bool size64, out bool offset64)
        {
            offset64 = this._offset >= 0xffffffffL;
            size64 = (offset64 || (this.SizeUncompressedLong >= 0xffffffffL)) || (this.SizeCompressedLong >= 0xffffffffL);
            if (size64 || offset64)
            {
                using (MemoryStream stream = new MemoryStream())
                {
                    using (BinaryWriter writer = new BinaryWriter(stream))
                    {
                        writer.Write((ushort)1);
                        int num = 0;
                        if (size64)
                        {
                            num += 0x10;
                        }
                        if (offset64 && central)
                        {
                            num += 8;
                        }
                        writer.Write((short)num);
                        if (size64)
                        {
                            writer.Write(this._szOriginal);
                            writer.Write(this._szComp);
                        }
                        if (offset64 && central)
                        {
                            writer.Write(this._offset);
                        }
                        writer.Flush();
                        return stream.ToArray();
                    }
                }
            }
            return null;
        }

        private static short GetStringLength(string str)
        {
            if (!string.IsNullOrEmpty(str))
            {
                return (short)ZipEncoding.Encoding.GetByteCount(str);
            }
            return 0;
        }

        private static uint GetUIntValue(long value, bool zip64)
        {
            return (zip64 ? unchecked((uint)(-1L)) : ((uint)value));
        }

        internal bool IsZip64()
        {
            return (((this.SizeUncompressedLong >= 0xffffffffL) || (this.SizeCompressedLong >= 0xffffffffL)) || (this._offset >= 0xffffffffL));
        }

        public Stream OpenReader()
        {
            return this._owner.OpenReader(this);
        }

        private void ProcessExtraFieldInfo(byte[] xInfo)
        {
            if ((xInfo != null) && (xInfo.Length > 4))
            {
                using (MemoryStream stream = new MemoryStream(xInfo))
                {
                    using (BinaryReader reader = new BinaryReader(stream))
                    {
                        while (reader.PeekChar() > -1)
                        {
                            ushort num = reader.ReadUInt16();
                            ushort size = reader.ReadUInt16();
                            if (num == 1)
                            {
                                if (size > 0x1c)
                                {
                                    throw new ZipFileException(StringTables.GetString("Inconsistent datasize for ZIP64 extra field."), this.FileName);
                                }
                                if (this._szOriginal == 0xffffffffL)
                                {
                                    this.CheckDouble(size);
                                    this._szOriginal = reader.ReadInt64();
                                    size = (ushort)(size - 8);
                                }
                                if (this._szComp == 0xffffffffL)
                                {
                                    this.CheckDouble(size);
                                    this._szComp = reader.ReadInt64();
                                    size = (ushort)(size - 8);
                                }
                                if (this._offset == 0xffffffffL)
                                {
                                    this.CheckDouble(size);
                                    this._offset = reader.ReadInt64();
                                    size = (ushort)(size - 8);
                                }
                            }
                            stream.Position += size;
                        }
                    }
                }
            }
        }

        internal bool ReadCentralHeader(Stream fs)
        {
            BinaryReader reader = new BinaryReader(fs);
            byte[] buf = reader.ReadBytes(CENTRAL_SIGN.Length);
            if ((buf.Length != CENTRAL_SIGN.Length) || !ZipFile.Match(buf, CENTRAL_SIGN, 0))
            {
                return false;
            }
            reader.ReadInt16();
            reader.ReadInt16();
            this._flags = reader.ReadInt16();
            this._method = reader.ReadInt16();
            this._modTime = reader.ReadInt16();
            this._modDate = reader.ReadInt16();
            this._crc32 = reader.ReadInt32();
            this._szComp = reader.ReadUInt32();
            this._szOriginal = reader.ReadUInt32();
            short count = reader.ReadInt16();
            short num2 = reader.ReadInt16();
            short num3 = reader.ReadInt16();
            reader.ReadInt16();
            this._attInternal = reader.ReadInt16();
            this._attExternal = reader.ReadInt32();
            this._offset = reader.ReadUInt32();
            byte[] bytes = reader.ReadBytes(count);
            if (bytes.Length != count)
            {
                throw new ZipFileException(StringTables.GetString("Zip file is corrupted (wrong entry name size)."), this.FileName);
            }
            this._fileName = ZipEncoding.Encoding.GetString(bytes, 0, bytes.Length);
            if (num2 > 0)
            {
                byte[] xInfo = reader.ReadBytes(num2);
                this.ProcessExtraFieldInfo(xInfo);
            }
            this._comment = string.Empty;
            if (num3 > 0)
            {
                bytes = reader.ReadBytes(num3);
                if (bytes.Length != num3)
                {
                    throw new ZipFileException(StringTables.GetString("Zip file is corrupted (wrong entry comment size)."), this.FileName);
                }
                this._comment = ZipEncoding.Encoding.GetString(bytes, 0, bytes.Length);
            }
            return true;
        }

        internal bool ReadLocalHeader(Stream fs)
        {
            fs.Position = this._owner._bytesBeforeZip + this._offset;
            BinaryReader reader = new BinaryReader(fs);
            byte[] buf = reader.ReadBytes(LOCAL_SIGN.Length);
            if ((buf.Length != LOCAL_SIGN.Length) || !ZipFile.Match(buf, LOCAL_SIGN, 0))
            {
                return false;
            }
            reader.ReadInt16();
            this._flags = reader.ReadInt16();
            this._method = reader.ReadInt16();
            this._modTime = reader.ReadInt16();
            this._modDate = reader.ReadInt16();
            int num = reader.ReadInt32();
            uint num2 = reader.ReadUInt32();
            uint num3 = reader.ReadUInt32();
            short count = reader.ReadInt16();
            short num5 = reader.ReadInt16();
            if (!this.HasDataDescriptor)
            {
                if (num != 0)
                {
                    this._crc32 = num;
                }
                if (num2 != uint.MaxValue)
                {
                    this._szComp = num2;
                }
                if (num3 != uint.MaxValue)
                {
                    this._szOriginal = num3;
                }
            }
            reader.ReadBytes(count);
            if (num5 > 0)
            {
                byte[] xInfo = reader.ReadBytes(num5);
                this.ProcessExtraFieldInfo(xInfo);
            }
            return true;
        }

        public void Remove()
        {
            this._owner.Remove(this);
        }

        private void SetFlags()
        {
            this._flags = 0;
            CompressionLevelEnum compressionLevel = this._owner.CompressionLevel;
            switch (compressionLevel)
            {
                case CompressionLevelEnum.NoCompression:
                    this._flags = 6;
                    break;

                case CompressionLevelEnum.BestSpeed:
                    this._flags = 4;
                    break;

                default:
                    if (compressionLevel == CompressionLevelEnum.BestCompression)
                    {
                        this._flags = 2;
                    }
                    break;
            }
            if (this._owner.Password.Length > 0)
            {
                this._flags = (short)(((ushort)this._flags) | 9);
            }
        }

        internal void SetNameAndFlags(string fileName, string entryName, DateTime dateTime)
        {
            entryName = AdjustDirSepChar(entryName);
            this._fileName = entryName;
            this._comment = string.Empty;
            this._method = 8;
            this.SetFlags();
            this.SetTime(dateTime);
            this._attExternal = 0x20;
            if ((fileName != null) && (File.Exists(fileName) || Directory.Exists(fileName)))
            {
                FileInfo info = new FileInfo(fileName);
                this._attExternal = (int)info.Attributes;
            }
        }

        private void SetTime(DateTime time)
        {
            int num = Math.Max(time.Year - 0x7bc, 0);
            this._modDate = (short)((time.Day + (time.Month << 5)) + (num << 9));
            this._modTime = (short)(((time.Second >> 1) + (time.Minute << 5)) + (time.Hour << 11));
        }

        private static DateTime ToDateTime(int wDosDate, int wDosTime)
        {
            ushort num = (ushort)wDosTime;
            ushort num2 = (ushort)wDosDate;
            int second = (num & 0x1f) << 1;
            int minute = (num >> 5) & 0x3f;
            int hour = (num >> 11) & 0x1f;
            int day = num2 & 0x1f;
            int month = (num2 >> 5) & 15;
            int year = ((num2 >> 9) & 0x7f) + 0x7bc;
            DateTime minValue = DateTime.MinValue;
            if ((month > 0) && (day > 0))
            {
                try
                {
                    minValue = new DateTime(year, month, day, hour, minute, second);
                }
                catch
                {
                }
            }
            return minValue;
        }

        internal uint WriteCentralHeader(Stream fs)
        {
            bool flag;
            bool flag2;
            long position = fs.Position;
            byte[] buffer = this.GetExtraFieldInfo(true, out flag, out flag2);
            BinaryWriter writer = new BinaryWriter(fs);
            writer.Write(CENTRAL_SIGN);
            writer.Write((ushort)0x2d);
            writer.Write((buffer != null) ? ((ushort)0x2d) : ((ushort)20));
            writer.Write(this._flags);
            writer.Write(this._method);
            writer.Write(this._modTime);
            writer.Write(this._modDate);
            writer.Write(this._crc32);
            writer.Write(GetUIntValue(this._szComp, flag));
            writer.Write(GetUIntValue(this._szOriginal, flag));
            writer.Write(GetStringLength(this.FileName));
            writer.Write((buffer != null) ? ((short)buffer.Length) : ((short)0));
            writer.Write(GetStringLength(this.Comment));
            writer.Write((short)0);
            writer.Write(this._attInternal);
            writer.Write(this._attExternal);
            writer.Write(GetUIntValue(this._offset, flag2));
            if (!string.IsNullOrEmpty(this.FileName))
            {
                writer.Write(ZipEncoding.Encoding.GetBytes(this.FileName));
            }
            if (buffer != null)
            {
                writer.Write(buffer);
            }
            if (!string.IsNullOrEmpty(this.Comment))
            {
                writer.Write(ZipEncoding.Encoding.GetBytes(this.Comment));
            }
            int num = (0x2e + GetStringLength(this.FileName)) + GetStringLength(this.Comment);
            if (buffer != null)
            {
                num += buffer.Length;
            }
            return (uint)num;
        }

        internal int WriteLocalHeader(Stream fs)
        {
            bool flag;
            bool flag2;
            byte[] buffer = this.GetExtraFieldInfo(false, out flag, out flag2);
            BinaryWriter writer = new BinaryWriter(fs);
            writer.Write(LOCAL_SIGN);
            writer.Write((buffer != null) ? ((ushort)0x2d) : ((ushort)20));
            writer.Write(this._flags);
            writer.Write(this._method);
            writer.Write(this._modTime);
            writer.Write(this._modDate);
            writer.Write(this._crc32);
            writer.Write(GetUIntValue(this._szComp, flag));
            writer.Write(GetUIntValue(this._szOriginal, flag));
            writer.Write(GetStringLength(this.FileName));
            writer.Write((buffer != null) ? ((short)buffer.Length) : ((short)0));
            if (!string.IsNullOrEmpty(this.FileName))
            {
                writer.Write(ZipEncoding.Encoding.GetBytes(this.FileName));
            }
            if (buffer != null)
            {
                writer.Write(buffer);
            }
            int num = 30 + GetStringLength(this.FileName);
            if (buffer != null)
            {
                num += buffer.Length;
            }
            return num;
        }

        // Properties
        public FileAttributes Attributes
        {
            get
            {
                return (FileAttributes)this._attExternal;
            }
        }

        public string Comment
        {
            get
            {
                return this._comment;
            }
            set
            {
                this._owner.SetEntryComment(this.FileName, value);
            }
        }

        public double CompressionRatio
        {
            get
            {
                if (this._szOriginal <= 0L)
                {
                    return 0.0;
                }
                return (1.0 - (((double)this._szComp) / ((double)this._szOriginal)));
            }
        }

        public int CRC32
        {
            get
            {
                return this._crc32;
            }
        }

        public DateTime Date
        {
            get
            {
                return ToDateTime(this._modDate, this._modTime);
            }
        }

        public string FileName
        {
            get
            {
                return this._fileName;
            }
        }

        internal bool HasDataDescriptor
        {
            get
            {
                return ((this._flags & 8) != 0);
            }
        }

        public bool IsEncrypted
        {
            get
            {
                return ((this._flags & 1) != 0);
            }
        }

        public int SizeCompressed
        {
            get
            {
                if (this._szComp > 0x7fffffffL)
                {
                    throw new ZipFileException(StringTables.GetString("Size is too large to be represented as an Int32, use SizeCompressedLong instead."), this.FileName);
                }
                return (int)this._szComp;
            }
        }

        public long SizeCompressedLong
        {
            get
            {
                return this._szComp;
            }
        }

        public int SizeUncompressed
        {
            get
            {
                if (this._szOriginal > 0x7fffffffL)
                {
                    throw new ZipFileException(StringTables.GetString("Size is too large to be represented as an Int32, use SizeUncompressedLong instead."), this.FileName);
                }
                return (int)this._szOriginal;
            }
        }

        public long SizeUncompressedLong
        {
            get
            {
                return this._szOriginal;
            }
        }
    }
}
